/*
 * lib/hexdump.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. See README and COPYING for
 * more details.
 */
#include <seminix/errno.h>
#include <seminix/ctype.h>
#include <seminix/kernel.h>
#include <asm/unaligned.h>

const char hex_asc[] = "0123456789abcdef";
const char hex_asc_upper[] = "0123456789ABCDEF";

/**
 * hex_to_bin - convert a hex digit to its real value
 * @ch: ascii character represents hex digit
 *
 * hex_to_bin() converts one hex digit to its actual value or -1 in case of bad
 * input.
 */
int hex_to_bin(char ch)
{
    if ((ch >= '0') && (ch <= '9'))
        return ch - '0';
    ch = tolower(ch);
    if ((ch >= 'a') && (ch <= 'f'))
        return ch - 'a' + 10;
    return -1;
}

/**
 * hex2bin - convert an ascii hexadecimal string to its binary representation
 * @dst: binary result
 * @src: ascii hexadecimal string
 * @count: result length
 *
 * Return 0 on success, -EINVAL in case of bad input.
 */
int hex2bin(u8 *dst, const char *src, usize count)
{
    while (count--) {
        int hi = hex_to_bin(*src++);
        int lo = hex_to_bin(*src++);

        if ((hi < 0) || (lo < 0))
            return -EINVAL;

        *dst++ = (hi << 4) | lo;
    }
    return 0;
}

/**
 * bin2hex - convert binary data to an ascii hexadecimal string
 * @dst: ascii hexadecimal result
 * @src: binary data
 * @count: binary data length
 */
char *bin2hex(char *dst, const void *src, usize count)
{
    const unsigned char *_src = src;

    while (count--)
        dst = hex_byte_pack(dst, *_src++);
    return dst;
}

/**
 * hex_dump_to_buffer - convert a blob of data to "hex ASCII" in memory
 * @buf: data blob to dump
 * @len: number of bytes in the @buf
 * @rowsize: number of bytes to print per line; must be 16 or 32
 * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
 * @linebuf: where to put the converted data
 * @linebuflen: total size of @linebuf, including space for terminating NUL
 * @ascii: include ASCII after the hex output
 *
 * hex_dump_to_buffer() works on one "line" of output at a time, i.e.,
 * 16 or 32 bytes of input data converted to hex + ASCII output.
 *
 * Given a buffer of u8 data, hex_dump_to_buffer() converts the input data
 * to a hex + ASCII dump at the supplied memory location.
 * The converted output is always NUL-terminated.
 *
 * E.g.:
 *   hex_dump_to_buffer(frame->data, frame->len, 16, 1,
 *			linebuf, sizeof(linebuf), true);
 *
 * example output buffer:
 * 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
 *
 * Return:
 * The amount of bytes placed in the buffer without terminating NUL. If the
 * output was truncated, then the return value is the number of bytes
 * (excluding the terminating NUL) which would have been written to the final
 * string if enough space had been available.
 */
int hex_dump_to_buffer(const void *buf, usize len, int rowsize, int groupsize,
               char *linebuf, usize linebuflen, bool ascii)
{
    const u8 *ptr = buf;
    int ngroups;
    u8 ch;
    int j, lx = 0;
    int ascii_column;
    int ret;

    if (rowsize != 16 && rowsize != 32)
        rowsize = 16;

    if ((int)len > rowsize)		/* limit to one line at a time */
        len = rowsize;
    if (!is_power_of_2(groupsize) || groupsize > 8)
        groupsize = 1;
    if ((len % groupsize) != 0)	/* no mixed size output */
        groupsize = 1;

    ngroups = len / groupsize;
    ascii_column = rowsize * 2 + rowsize / groupsize + 1;

    if (!linebuflen)
        goto overflow1;

    if (!len)
        goto nil;

    if (groupsize == 8) {
        const u64 *ptr8 = buf;

        for (j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                       "%s%16.16llx", j ? " " : "",
                       get_unaligned((unsigned long long *)ptr8 + j));
            if (ret >= (int)linebuflen - lx)
                goto overflow1;
            lx += ret;
        }
    } else if (groupsize == 4) {
        const u32 *ptr4 = buf;

        for (j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                       "%s%8.8x", j ? " " : "",
                       get_unaligned(ptr4 + j));
            if (ret >= (int)linebuflen - lx)
                goto overflow1;
            lx += ret;
        }
    } else if (groupsize == 2) {
        const u16 *ptr2 = buf;

        for (j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                       "%s%4.4x", j ? " " : "",
                       get_unaligned(ptr2 + j));
            if (ret >= (int)linebuflen - lx)
                goto overflow1;
            lx += ret;
        }
    } else {
        for (j = 0; j < (int)len; j++) {
            if ((int)linebuflen < lx + 2)
                goto overflow2;
            ch = ptr[j];
            linebuf[lx++] = hex_asc_hi(ch);
            if ((int)linebuflen < lx + 2)
                goto overflow2;
            linebuf[lx++] = hex_asc_lo(ch);
            if ((int)linebuflen < lx + 2)
                goto overflow2;
            linebuf[lx++] = ' ';
        }
        if (j)
            lx--;
    }
    if (!ascii)
        goto nil;

    while (lx < ascii_column) {
        if ((int)linebuflen < lx + 2)
            goto overflow2;
        linebuf[lx++] = ' ';
    }
    for (j = 0; j < (int)len; j++) {
        if ((int)linebuflen < lx + 2)
            goto overflow2;
        ch = ptr[j];
        linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.';
    }
nil:
    linebuf[lx] = '\0';
    return lx;
overflow2:
    linebuf[lx++] = '\0';
overflow1:
    return ascii ? ascii_column + (int)len : (groupsize * 2 + 1) * ngroups - 1;
}

void print_hex_dump(const char *level, const char *prefix_str,
               int prefix_type, int rowsize, int groupsize,
               const void *buf, usize len, bool ascii,
               __printf(1, 2) int (*print)(const char *, ...))
{
    const u8 *ptr = buf;
    int i, linelen, remaining = len;
    unsigned char linebuf[32 * 3 + 2 + 32 + 1];

    if (rowsize != 16 && rowsize != 32)
        rowsize = 16;

    for (i = 0; i < (int)len; i += rowsize) {
        linelen = min(remaining, rowsize);
        remaining -= rowsize;

        hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
                   (char *)linebuf, sizeof(linebuf), ascii);

        switch (prefix_type) {
        case DUMP_PREFIX_ADDRESS:
            print("%s%s%p: %s\n",
                   level, prefix_str, ptr + i, linebuf);
            break;
        case DUMP_PREFIX_OFFSET:
            print("%s%s%.8x: %s\n", level, prefix_str, i, linebuf);
            break;
        default:
            print("%s%s%s\n", level, prefix_str, linebuf);
            break;
        }
    }
}
